package model.primitive;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.vecmath.Point2f;
import javax.vecmath.Point3d;
import javax.vecmath.Tuple3d;
import javax.vecmath.Vector3d;

import model.ray.Ray;
import model.shader.Shader;
import model.utils.Putils;
import raytracing.RayTracer;

public class TriangleOld extends Primitive implements Comparable<TriangleOld> {
       private Point3d[] vertices;

       public Vector3d[] normals= new Vector3d[3];

       private Point3d center = null;          //Centro del triangulo
       private double distance = 0.0;          //Distancia a la camara

       double u,v,w; // coordenadas baricentricas

	private Vector3d normal;
	private Plane trianglePlane;


   public TriangleOld(String name, Shader shader, Point3d v0, Point3d v1, Point3d v2) {
           super(shader, name);

           if (v0 == null || v1 == null || v2 == null) {
                   throw new IllegalArgumentException("null arguments recieved");
           }
           if (v0.equals(v1) || v0.equals(v2) || v1.equals(v2)) {
                   throw new IllegalArgumentException("Vertex coordinates must bedifferent among each other");
           }

           this.vertices = new Point3d[3];
           this.vertices[0] = new Point3d(v0);
           this.vertices[1] = new Point3d(v1);
           this.vertices[2] = new Point3d(v2);
           calcTriangleNormal();
           this.trianglePlane = new Plane(this.getShader(), this.getName(), this.vertices[0], this.normal);
   }

   public TriangleOld(String name, Shader shader, Point3d[] vertices) {
           super(shader, name);

           if (vertices.length != 3) {
                   throw new IllegalArgumentException("Exactly 3 vertices are required");
               }

               if (vertices[0].equals(vertices[1]) ||
vertices[0].equals(vertices[2]) || vertices[1].equals(vertices[2])) {
                       throw new IllegalArgumentException("Exactly 3 vertices are required");
           }

           this.vertices = new Point3d[3];
           this.vertices[0] = new Point3d(vertices[0]);
           this.vertices[1] = new Point3d(vertices[1]);
           this.vertices[2] = new Point3d(vertices[2]);
           calcTriangleNormal();
           this.trianglePlane = new Plane(this.getShader(), this.getName(), this.vertices[0], this.normal);
   }

       


     //Version de tomi	   	
   @Override
	public List<Point3d> getIntersections(Ray ray) {
        
		double missCondition;
		int index[] = {1, 2, 0}; /* indices para el ciclo for */
		
		Point3d interPoint = null; /* Punto de interseccion entre el plano del triangulo y el rayo */
		Vector3d u,v;
		
		missCondition = 0;
		
		List<Point3d> intersections = new ArrayList<Point3d>();
		intersections = this.trianglePlane.getIntersections(ray); 
		if( intersections != null && intersections.size() > 0 )
			interPoint = intersections.get(0);
		
		if (interPoint == null) {
			return null; // no intersecta ni con el plano del triangulo
		}
		
		/* 
		 * Metodo de los medios planos (half-plane) */
		
		u = new Vector3d();
		v = new Vector3d();
		
		
		for (int i = 0 ; i < this.vertices.length && missCondition >= 0 ; i++) {
			u.sub(this.vertices[index[i]], this.vertices[i]); /* Vector que representa 1 lado del triangulo */
			v.sub(interPoint, this.vertices[i]);
			u.cross(u, v);
			missCondition = u.dot(this.normal);
		}
		
		if (missCondition < 0) {
			return null;
		}
		ArrayList<Point3d> ret = new ArrayList<Point3d>();
		ret.add(interPoint);
		return ret;
	}
   	
   	
   	
   	//Version de jose
	
       public List<Point3d> getIntersections_jose(Ray ray) {

    	   boolean debug = false;
           if( ray.direction.x == 0.09465860336425064 &&
    			   ray.direction.y == 0.2037944099849574 &&
    			   	ray.direction.z == -0.9744267993379556 ) {
    			debug = true;
    			System.out.println(this);
     	   }
           
           
               Vector3d v1 = new Vector3d(vertices[0]);		//102/012
               Vector3d v2 = new Vector3d(vertices[1]);		//021/120
               Vector3d v3 = new Vector3d(vertices[2]);		//210/201

               Vector3d edge1 = new Vector3d();
               Vector3d edge2 = new Vector3d();

               edge1.sub(v2,v1);
               edge2.sub(v3,v1);

               Vector3d s1= new Vector3d();

               s1.cross(ray.getDirection(),edge2);

               //producto escalar. Ver para que se hace
               double divisor = s1.dot(edge1);

if( debug ) { System.out.println("divisor " + divisor); }
               
				// si es 0, que pasa?
               if( Double.compare(divisor,0) == 0 ) {
RayTracer.triDiv.add(this);
					return null;
               }

               divisor = 1/divisor;
               Vector3d distance = new Vector3d(ray.getOrigin());
               distance.sub(v1);

               //producto esalar, que sera esto?
               double bary1 = distance.dot(s1) * divisor;
               
if( debug ) { 
	System.out.println("bary1  " + bary1);
}

               if( bary1 < 0 || bary1 > 1){
RayTracer.triOff.put(this, new Ray(new Point3d(ray.origin), new Vector3d(ray.direction)));
                    return null;
               }

               Vector3d s2= new Vector3d();
               s2.cross(distance,edge1);

               double bary2 = ray.getDirection().dot(s2) * divisor;
               
if( debug ) { System.out.println("bary2  " + bary2); }

               if( bary2 < 0 || (bary2 + bary1) > 2){
            	   		RayTracer.triOff.put(this, new Ray(new Point3d(ray.origin), new Vector3d(ray.direction)));
                       return null;
               }
               edge1.scale(bary1);
               edge2.scale(bary2);
               v1.add(edge1);
               v1.add(edge2);

               List<Point3d> r= new ArrayList<Point3d>();
               r.add(new Point3d(v1));
               
               RayTracer.triOn.add(this);
               return r;
       }

       @Override
       public String toString() {
               StringBuffer sb = new StringBuffer();
               for (Point3d p: this.vertices) {
                       sb.append(p).append(" ");
               }
               return sb.toString();
       }

       private void calcTriangleNormal() {

               Vector3d u = new Vector3d();
               Vector3d v = new Vector3d();
               u.sub(vertices[0],vertices[1]);
               v.sub(vertices[0],vertices[2]);
               u.cross(u, v);
               this.normal = new Vector3d(u);
       }

       @Override
       public void translate(Point3d pos) {
               if( pos != null )       {
                       this.vertices[0].add(pos);
                       this.vertices[1].add(pos);
                       this.vertices[2].add(pos);
                       calcTriangleNormal();
               }
       }

       public Point3d[] getVertex() {
               return this.vertices;
       }

       @Override
       public Vector3d getNormal(Point3d pos) {

               Vector3d n0 = new Vector3d(normals[0]);
               Vector3d n1 = new Vector3d(normals[1]);
               Vector3d n2 = new Vector3d(normals[2]);

               double aT= triarea(vertices[0],vertices[1],vertices[2]);
               double aB= triarea(vertices[0],pos,vertices[2]);
               double aC= triarea(vertices[0],pos,vertices[1]);

               double c2=aB/aT;
               double c3=aC/aT;

               n1.sub(n0);
               n2.sub(n0);

               n1.scale(c2);
               n2.scale(c3);

               n0.add(n1);
               n0.add(n2);
               n0.normalize();

               return n0;
       }

       private double triarea(Tuple3d p1, Tuple3d p2, Tuple3d p3) {

               Vector3d a = new Vector3d();
               Vector3d b = new Vector3d();
               Vector3d c = new Vector3d();

               a.sub(p1,p2);
               b.sub(p1,p3);
               c.sub(p2,p3);

               double la=a.length();
               double lb=b.length();
               double lc=c.length();

               double s=(la+lb+lc)/2.0f;
               double data=s*(s-la)*(s-lb)*(s-lc);
               double r=Math.sqrt(data);
               return r;
       }

       @Override
       public void rotatex(Double d) {
               if(d!=null){
                       this.vertices=Putils.rotatex(this.vertices,d);
                       this.normals=Putils.rotatex(this.normals, d);
                       auxCalc();
               }
       }

       @Override
       public void rotatey(Double d) {
               if(d!=null){
                       this.vertices=Putils.rotatey(this.vertices,d);
                       this.normals=Putils.rotatey(this.normals,d);
                       auxCalc();
               }
       }

       @Override
       public void rotatez(Double d) {
               if(d!=null){
                       this.vertices= Putils.rotatez(this.vertices,d);
                       this.normals= Putils.rotatez(this.normals,d);
                       auxCalc();
               }
       }

       @Override
       public void scalex(Double s) {
               if(s!=null && s>0){
                       this.vertices[0]=Putils.scalex(this.vertices[0],s);
                       this.vertices[1]=Putils.scalex(this.vertices[1],s);
                       this.vertices[2]=Putils.scalex(this.vertices[2],s);
                       auxCalc();
               }
       }

       @Override
       public void scaley(Double s) {
               if(s!=null && s>0){
                       this.vertices[0]=Putils.scaley(this.vertices[0],s);
                       this.vertices[1]=Putils.scaley(this.vertices[1],s);
                       this.vertices[2]=Putils.scaley(this.vertices[2],s);
                       auxCalc();
               }
       }

       @Override
       public void scalez(Double s) {
               if(s!=null && s>0){
                       this.vertices[0]=Putils.scalez(this.vertices[0],s);
                       this.vertices[1]=Putils.scalez(this.vertices[1],s);
                       this.vertices[2]=Putils.scalez(this.vertices[2],s);
                       auxCalc();
               }
       }

       @Override
       public void scaleu(Double s) {
               if (s!=null && s>0){

                       this.vertices[0].scale(s);
                       this.vertices[1].scale(s);
                       this.vertices[2].scale(s);
                       auxCalc();
               }
       }

       private void auxCalc(){
               calcTriangleNormal();
       }

       @Override
       public Point2f getUV(Point3d p) {

       Vector3d u = new Vector3d();
       Vector3d v = new Vector3d();
       Vector3d v2 = new Vector3d();

       Point3d p1 = vertices[0];
       Point3d p2 = vertices[1];
       Point3d p3 = vertices[2];

       u.sub(p3,p2);
       v2.sub(p,p2);

       Vector3d aux= new Vector3d();
       aux.sub(p1,p);

       double distp = aux.length();
       v.sub(p1,p2);
       double du=u.length();
       double dv=v.length();
       u.normalize();
       v2.normalize();
       double cost= u.dot(v2);
       double t = Math.acos(cost);
       float disty=0,distx=0;
       disty=(float) (du-distp*Math.cos(t));
       distx=(float) (dv-distp*Math.sin(t));

       float x1=0,y1=0;

       y1= getCoord(0, du, p3.y, p2.y, disty);
       x1= getCoord(0, dv, p1.x, p2.x, distx);

       if( (x1 + y1) > 1 || (x1+ y1) < 0 )
               System.out.println( "Error en mapedo de texturas" );

       Point2f r= new Point2f(x1,y1);
       return r;
       }

       private float getCoord(double i1, double i2, double w1, double w2,
double  p){
               return (float) (((p-i1)/(i2-i1))*(w2-w1)+w1);
       }

       @Override
       public int compareTo(TriangleOld triangle) {
               int ret;
               if (this.distance > triangle.distance) {
                       ret = 1;
               } else if (this.distance < triangle.distance) {
                       ret = -1;
               } else {
                       ret = 0;
               }
               return ret;
       }

       public void setDistance(Vector3d vector) {
               if( this.center == null ) {
                       this.setCenter();
               }
               Vector3d aux = new Vector3d();
               aux.sub(this.center, vector);
               this.distance = aux.length();
       }

       private void setCenter() {
               Point3d center = new Point3d();
               center.set( (vertices[0].x + vertices[1].x + vertices[2].x) / 3,
                                       (vertices[0].y + vertices[1].y + vertices[2].y) / 3,
                                       (vertices[0].z + vertices[1].z + vertices[2].z) / 3 );
               this.center = center;
       }

       public Point3d getDmin() {
               Point3d min = new Point3d();
               min.x = Math.min(Math.min(vertices[0].x, vertices[1].x), vertices[2].x);
               min.y = Math.min(Math.min(vertices[0].y, vertices[1].y), vertices[2].y);
               min.z = Math.min(Math.min(vertices[0].z, vertices[1].z), vertices[2].z);
               return min;
       }

       public Point3d getDmax() {
               Point3d max = new Point3d();
               max.x = Math.max(Math.max(vertices[0].x, vertices[1].x), vertices[2].x);
               max.y = Math.max(Math.max(vertices[0].y, vertices[1].y), vertices[2].y);
               max.z = Math.max(Math.max(vertices[0].z, vertices[1].z), vertices[2].z);
               return max;
       }

       @Override
       public int hashCode() {
               final int prime = 31;
               int result = 1;
               result = prime * result + Arrays.hashCode(normals);
               result = prime * result + Arrays.hashCode(vertices);
               return result;
       }

       @Override
       public boolean equals(Object obj) {
               if (this == obj)
                       return true;
               if (obj == null)
                       return false;
               if (getClass() != obj.getClass())
                       return false;
               TriangleOld other = (TriangleOld) obj;
               if (Arrays.equals(normals, other.normals) && Arrays.equals(vertices,
other.vertices))
                       return false;
               return true;
       }
}
